using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using MacomberMap.Common.Components;
using MacomberMap.Common.Internals;
using MacomberMap.Common.Logging;
using System.Diagnostics;
using MacomberMap.Common.Types;
using MacomberMap.Common.Database_Interface;
using System.Drawing;
using MacomberMap.Common.Database_Interface.CIM.Components;
using MacomberMap.Common.Database_Interface.CIM.CIM_Interface;

namespace MacomberMap.Common.Database_Interface.CIM.One_Line_Interface
{
    /// <summary>
    /// Convert a DDL file into an XML file
    /// </summary>
    public class MM_OneLine_Converter
    {
        #region Variable declarations
        /// <summary>Our collection of WebFG colors</summary>
        public Dictionary<String, Color> WebFGColors;

        /// <summary>The XML comprising the element set</summary>
        public XmlDocument ESet;

        /// <summary>The XML comprising the display set</summary>
        public XmlDocument DSet;

        /// <summary>The element around which the one-line is imported</summary>
        public CIM_Element BaseElement;

        /// <summary>Our collection of item names and their associated potential elements</summary>
        public Dictionary<String, List<CIM_Element>> ItemNames = new Dictionary<string, List<CIM_Element>>();

        /// <summary>Our collection of elements retrieved from the one-line</summary>
        public Dictionary<MM_OneLine_XmlComponent, CIM_Element> DDLElements = new Dictionary<MM_OneLine_XmlComponent, CIM_Element>();

        /// <summary>Our collection of substation long names</summary>
        public Dictionary<String, String> SubstationLongNames;        
        #endregion

        #region Initialization
        /// <summary>
        /// Prepre to load in a DDL
        /// </summary>
        /// <param name="ColorPath">The path to the WebFG colors file</param>
        /// <param name="ESetPath">The path to the WebFG ESet</param>    
        /// <param name="DbConnection">Our database connection</param>
        public MM_OneLine_Converter(String ColorPath, String ESetPath, MM_Database DbConnection)
        {
            //Load the WebFG Colors into memory
            WebFGColors = new Dictionary<string, Color>();
            using (StreamReader sRd = new StreamReader(ColorPath))
                while (!sRd.EndOfStream)
                {
                    String[] InLine = sRd.ReadLine().Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
                    if (InLine.Length == 4)
                        WebFGColors.Add(InLine[3], Color.FromArgb(int.Parse(InLine[0]), int.Parse(InLine[1]), int.Parse(InLine[2])));
                }

            //Load our ESet into memory
            ESet = MM_OneLine_Parser.ReadDDL(ESetPath);

            //Load our substation long names into memory
            SubstationLongNames = new Dictionary<string, string>();
            foreach (MM_Substation Sub in MM_Repository.Substations.Values)
                if (SubstationLongNames.ContainsKey(Sub.LongName))
                {
                    String OldName = SubstationLongNames[Sub.LongName];
                    SubstationLongNames.Remove(Sub.LongName);
                    SubstationLongNames.Add(Sub.LongName + " (" + OldName + ")", OldName);
                    SubstationLongNames.Add(Sub.LongName + " (" + Sub.Name + ")", Sub.Name);
                }
                else

                    SubstationLongNames.Add(Sub.LongName, Sub.Name);
        }

        /// <summary>
        /// Add an element to our base list
        /// </summary>
        /// <param name="Elem">The element to be added</param>
        /// <param name="BaseElement">The base element (if a substation, prevents the current substation's name from being added to the list.</param>
        private void AddElementToBaseList(CIM_Element Elem, CIM_Element BaseElement)
        {
            List<String> WordsToAdd = new List<string>(3);
            WordsToAdd.Add(MM_Repository.TitleCase(Elem.Name));
            if (Elem.Type == "cim:Line")
                foreach (CIM_Element LineStation in Elem.ACSegmentStations)
                    if (LineStation != BaseElement)
                    {
                        String SubName = MM_Repository.TitleCase(LineStation.Name);                        
                        String SubDescription = MM_Repository.TitleCase(LineStation["cim:IdentifiedObject.description"]);
                        if (!WordsToAdd.Contains(SubName))
                            WordsToAdd.Add(SubName);
                        if (!String.IsNullOrEmpty(SubDescription) && !WordsToAdd.Contains(SubDescription))
                            WordsToAdd.Add(SubDescription);
                        if (SubstationLongNames.ContainsKey(SubName) && !WordsToAdd.Contains(SubstationLongNames[SubName]))
                            WordsToAdd.Add(SubstationLongNames[SubName]);
                    }
            foreach (String ElemName in WordsToAdd)
            {                
                if (ItemNames.ContainsKey(ElemName))
                    ItemNames[ElemName].Add(Elem);
                else
                {
                    List<CIM_Element> ElemList = new List<CIM_Element>(10);
                    ElemList.Add(Elem);
                    ItemNames.Add(ElemName, ElemList);
                }
            }
        }

        /// <summary>
        /// Load a display set (for a particular substation) into memory and process it
        /// </summary>
        /// <param name="DSetPath">The path to the DSet</param>
        /// <param name="BaseElement">The element upon which the one-line should be based</param>
        public XmlDocument ProcessDSet(String DSetPath, CIM_Element BaseElement)
        {
            //Build our list of connected elements
            ItemNames.Clear();
            this.BaseElement = BaseElement;            
            if (BaseElement.Type == "cim:Substation")
                foreach (CIM_Element Elem in BaseElement.Elements)
                    AddElementToBaseList(Elem, BaseElement);
            else
                foreach (CIM_Element Elem in CIM_Tracer.Tracer(BaseElement, true, true, null).Keys)
                    AddElementToBaseList(Elem, BaseElement);


            //Create our outgoing XML document
            DSet = MM_OneLine_Parser.ReadDDL(DSetPath);
            XmlDocument OutDoc = new XmlDocument();            
            XmlElement DocRoot = OutDoc.CreateElement("One_Line");
            OutDoc.AppendChild(DocRoot);
            DocRoot.Attributes.Append(OutDoc.CreateAttribute("Type")).Value = BaseElement.Type.Split(':')[1];
            DocRoot.Attributes.Append(OutDoc.CreateAttribute("Name")).Value = BaseElement.Name;
            DocRoot.Attributes.Append(OutDoc.CreateAttribute("Base_Element")).Value = BaseElement.TEID;


            //Integrate all of our Xml Elements
            foreach (XmlElement xE in DSet.SelectSingleNode("DDL/display/simple_layer").ChildNodes)
                IntegrateElement(xE);                                  

            //Go through all of our elements, and pull in the markers that aren't in the title, and link the ones we can
            DDLElements.Clear();
            List<MM_OneLine_XmlComponent> Components = new List<MM_OneLine_XmlComponent>();
            List<MM_OneLine_XmlComponent> Buses = new List<MM_OneLine_XmlComponent>();
            List<MM_OneLine_XmlComponent> Nodes = new List<MM_OneLine_XmlComponent>();
            foreach (XmlElement xE in DSet["DDL"]["display"]["simple_layer"].ChildNodes)
            {
                MM_OneLine_XmlComponent NewElem = new MM_OneLine_XmlComponent(xE,null);
                if (NewElem.ElementRectangle.Top > 32)
                {
                    DDLElements.Add(NewElem, null);
                    if (!String.IsNullOrEmpty(NewElem.Key))
                    {
                        String KeyToSearch = MM_Repository.TitleCase(NewElem.Key.Substring(NewElem.Key.LastIndexOf('=') + 1));
                        if (ItemNames.ContainsKey(KeyToSearch) && ItemNames[KeyToSearch].Count == 1)
                            NewElem.AssociatedElement = ItemNames[KeyToSearch][0];
                        else if (ItemNames.ContainsKey(KeyToSearch))
                        {
                            Debug.WriteLine("Multiple possilibities for element " + KeyToSearch + ". Enhancing search by type.");
                            foreach (CIM_Element Elem in ItemNames[KeyToSearch])
                                if (NewElem.CIMType == Elem.Type)
                                    NewElem.AssociatedElement = Elem;
                        }
                    }
                    
                    if (NewElem.Name.EndsWith("_BUS"))
                        Buses.Add(NewElem);
                    else if (NewElem.Name.StartsWith("CONN_"))
                        Nodes.Add(NewElem);
                    else if (NewElem.CanIntegrate)
                        Components.Add(NewElem);
                }
            }                                                 

            //Parse all integratable components
            foreach (MM_OneLine_XmlComponent Component in Components)
            {
                Component.IntegrateComponents(DDLElements);               
                DocRoot.AppendChild(Component.CreateXml(OutDoc));
            }
                        
            //Now, integrate our buses.           
            foreach (MM_OneLine_XmlComponent Bus in Buses)
                if (DDLElements.ContainsKey(Bus))
                {
                    Bus.IntegrateComponents(DDLElements);
                    Bus.IntegrateNode(DDLElements);
                    DocRoot.AppendChild(Bus.CreateXml(OutDoc));
                }

            //Integrate our nodes.
            foreach (MM_OneLine_XmlComponent Node in Nodes)
                if (DDLElements.ContainsKey(Node))
                {
                    Node.IntegrateNode(DDLElements);
                    DocRoot.AppendChild(Node.CreateXml(OutDoc));
                }

            
            //Now, go through all of our remaining elements, and write out our Xml.
            foreach (MM_OneLine_XmlComponent Elem in DDLElements.Keys)
                if (!Components.Contains(Elem) && !Buses.Contains(Elem) && !Nodes.Contains(Elem))
                    DocRoot.AppendChild(Elem.CreateXml(OutDoc));
                
            return OutDoc;
        }

        /// <summary>
        /// Recursively go through the current node, and pull in picture, symbol, gab, etc., information in 
        /// </summary>
        /// <param name="ElemToIntegrate">The element to be integrated</param>
        private void IntegrateElement(XmlElement ElemToIntegrate)
        {
            //Import our picture information and such from the ESET
            if (ElemToIntegrate.Name == "picture")
                MergeXml(ElemToIntegrate, ESet.SelectSingleNode("DDL/picture[@Name='" + ElemToIntegrate.Attributes["Name"].Value + "']") as XmlElement);

            //Now, go through everything, and import our symbols
            if (ElemToIntegrate.Name == "symbol")
                MergeXml(ElemToIntegrate, ESet.SelectSingleNode("DDL/symbol[@Name='" + ElemToIntegrate.Attributes["Name"].Value + "']") as XmlElement);

            //Now, go through everything, and roll in our cams.
            if (ElemToIntegrate.HasAttribute("cam"))
                MergeXml(ElemToIntegrate, ESet.SelectSingleNode("DDL/cam[@Name='" + ElemToIntegrate.Attributes["cam"].Value + "']") as XmlElement);

            //Now, go through everything, and roll in our gabs.
            if (ElemToIntegrate.HasAttribute("gab"))
                MergeXml(ElemToIntegrate, ESet.SelectSingleNode("DDL/gab[@Name='" + ElemToIntegrate.Attributes["gab"].Value + "']") as XmlElement);

            //Now, do the same for all child nodes.
            foreach (XmlElement xE2 in ElemToIntegrate.ChildNodes)
                IntegrateElement(xE2);
        }

        /// <summary>
        /// Import attributes and elements from an eset node (attributes and sub-elements) into a dset element
        /// </summary>
        /// <param name="DSetElement">The DSet element (target)</param>
        /// <param name="ESetElement">The ESet element (source)</param>
        private void MergeXml(XmlElement DSetElement, XmlElement ESetElement)
        {
            foreach (XmlAttribute xAttr in ESetElement.Attributes)
                if (xAttr.Name != "Name")
                    DSetElement.Attributes.Append(DSetElement.OwnerDocument.ImportNode(xAttr, false) as XmlAttribute);
            foreach (XmlElement xElem2 in ESetElement.ChildNodes)
                DSetElement.AppendChild(DSetElement.OwnerDocument.ImportNode(xElem2, true));
        }
        #endregion
    }
}
